﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestByWire.AttributeHandlers;

namespace TestByWire
{
    public interface ITestTargetBuilder
    {
        object InitializeTestTarget();
    }

    internal class TestTargetBuilder : ITestTargetBuilder
    {
        private readonly IStack _stack;

        public TestTargetBuilder()
            : this(new Stack())
        {
        }

        public TestTargetBuilder(IStack stack)
        {
            if (stack == null)
            {
                throw new ArgumentNullException("stack");
            }

            _stack = stack;
        }

        public object InitializeTestTarget()
        {
            ScopeInformation scopeInfo = null;

            foreach (var stackFrame in _stack.GetStackFrames())
            {
                if (stackFrame.IsDefined(typeof(TestMethodAttribute)))
                {
                    scopeInfo = new ScopeInformation(stackFrame.Method, scopeInfo);
                    IAttributeBuildPlan buildPlan = new AttributeBuildPlan(scopeInfo);
                    TargetInvoker.Container.RegisterInstance(PipelinePosition.PreCreation.ToString(), buildPlan);
                    TargetInvoker.Container.RegisterInstance(PipelinePosition.PostCreation.ToString(), buildPlan);
                    break;
                }
            }

            return TargetInvoker.Container.Resolve(TargetInvoker.TargetTypeInContext);
        }
    }

    internal class Stack : IStack
    {
        public IEnumerable<IStackFrameInfo> GetStackFrames()
        {
            var stackTrace = new StackTrace();

            foreach (StackFrame stackFrame in stackTrace.GetFrames())
            {
                yield return new StackFrameInfo(stackFrame);
            }
        }
    }

    internal class StackFrameInfo : IStackFrameInfo
    {
        private readonly StackFrame _stackFrame;

        public StackFrameInfo(StackFrame stackFrame)
        {
            _stackFrame = stackFrame;
        }

        public MethodInfo Method
        {
            get
            {
                return (MethodInfo)_stackFrame.GetMethod();
            }
        }

        public bool IsDefined(Type attributeType)
        {
            return Method.IsDefined(typeof(TestMethodAttribute), true);
        }
    }

    internal interface IStack
    {
        IEnumerable<IStackFrameInfo> GetStackFrames();
    }

    internal interface IStackFrameInfo
    {
        MethodInfo Method { get; }
        bool IsDefined(Type attributeType);
    }
}